/*
 * IMLocalStorageM.nc -- local storage component
 *
 * Copyright (C) Andrey Vihrov <andrey.vihrov@gmail.com>, 2010
 * Copyright (C) Askar Salimbaev <ghostinshell@gmail.com>, 2010
 *
 * This file is part of Intermote.
 *
 * Intermote is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Intermote is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Intermote.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <Intermote.h>

// TODO: Check if volume should be initialized
// TODO: Implement replication calls
module IMLocalStorageM {

	provides {
		interface IMBlockStorage as BlockStorage[uint8_t id];
		interface IMBlockProperty as BlockProperty[uint8_t, uint8_t];
		interface Init[uint8_t id];

		/* FIXME Only temporarily */
		interface SplitControl as LocalControl[uint8_t id];
	}

	uses interface BlockRead as BlockRead[ uint8_t id ];
	uses interface IMReplication as Replication;
	uses interface IMBlockWriteExt as BlockWrite[ uint8_t id ];

	uses interface IMLocalAddress as LocalAddress;
	uses interface IMBlockId as BlockId;

}
implementation {
	enum {
		NUM_BLOCKS = uniqueCount( "IMLocalStorage" ),
	};

	typedef enum {
		S_IDLE,
		S_ALLOC,
		S_READ,
		S_RESERVE,
	S_PROPERTY,
		S_WRITE,
		S_ERASE,
	} LocalStorageReq_t;

	typedef struct LocalStorageState {
		LocalStorageReq_t req;
		im_block_id_t value;
		uint32_t buf;
	} LocalStorageState_t;

	LocalStorageState_t states[ NUM_BLOCKS ];
	const uint8_t allocationMark=0;
	im_block_id_t blockCount=0;
	im_block_id_t nextBlock=0;
	bool initDone = 0;
	bool globalLock = 0;

	/* Useful functions */
	storage_addr_t getPhysicalAddress(im_block_id_t block) {
		// calculate physical address
		return (block << IM_BLOCK_SIZE_LOG2);
	}
	storage_addr_t getPropAddress(im_block_id_t block,  uint8_t prop) {
		// calculate property address
		return getPhysicalAddress(block) + 1 + (4 * prop);
	}
	storage_addr_t getDataAddress(im_block_id_t block, storage_addr_t offset) {
		// calculate block data address
		return getPhysicalAddress(block) + offset + (4 * IM_PROP_COUNT + 1);
	}


	/* LocalControl interface operations */

	int started = 0;

	command error_t LocalControl.start[uint8_t id] (void)
	{
		/* Free one block for upper layer */
		call BlockStorage.erase[id](0);
		return SUCCESS;
	}

	command error_t LocalControl.stop[uint8_t id] (void)
	{
		return FAIL;
	}

	default event void LocalControl.startDone[uint8_t id] (error_t err) { }
	default event void LocalControl.stopDone[uint8_t id] (error_t err) { }


/* BlockStorage interface operations */

	command error_t Init.init[uint8_t id] () {
		storage_len_t size;
		if(initDone) {
			return SUCCESS; // Don't initialize twice
		}
		size = call BlockRead.getSize[id]();
		blockCount = (size >> IM_BLOCK_SIZE_LOG2);
		initDone = 1;
		return SUCCESS;
	}

	command	error_t BlockStorage.allocate[ uint8_t id ] (void) {
		atomic { // atomic to prevent simultaneous allocations
			if (globalLock || states[id].req != S_IDLE) {
				return FAIL;
			}
			globalLock = 1;
			states[id].req = S_ALLOC;
		}
		states[id].value = nextBlock;

		call BlockRead.read[id](getPhysicalAddress(states[id].value),&states[id].buf,1);
		return SUCCESS;
	}

	command error_t BlockStorage.erase[ uint8_t id ] (im_block_id_t block) {
		if (states[id].req != S_IDLE) {
			return FAIL;
		}
		call BlockWrite.erase[id](block);
		// Erase replicated blocks
		call Replication.erase(block);
		return SUCCESS;
	}

	command error_t BlockStorage.read[ uint8_t id ] (im_block_id_t block, im_off_t offset, struct im_chunk *buf) {
		if (states[id].req != S_IDLE) {
			return FAIL;
		}
		states[id].req = S_READ;

		return call BlockRead.read[id](getDataAddress(block, offset), buf->data, IM_CHUNK_SIZE);
	}

	command error_t BlockStorage.write[ uint8_t id ] (im_block_id_t block, im_off_t offset, const struct im_chunk *buf) {
		if (states[id].req != S_IDLE) {
			return FAIL;
		}
		states[id].req = S_WRITE;
		call BlockWrite.write[id](getDataAddress(block, offset), (void*)buf->data, IM_CHUNK_SIZE);
		// Replicates changes
		call Replication.write(block, offset, (void*)buf->data);
		return SUCCESS;
	}

	command error_t BlockStorage.reserve[ uint8_t id ] (uint8_t count) {
		// TODO: To be implemented
		return FAIL;
	}

	default event void BlockStorage.writeDone[uint8_t id] (error_t error) {
	}

	default event void BlockStorage.readDone[uint8_t id] (error_t error) {
	}

	default event void BlockStorage.eraseDone[uint8_t id] (error_t error) {
	}

	default event void BlockStorage.allocateDone[uint8_t id] (error_t error,im_block_id_t block) {
	}

	/* BlockProperty interface operations */

	command error_t BlockProperty.set[uint8_t id, uint8_t prop] (im_block_id_t block, uint32_t data) {
		if (states[id].req != S_IDLE) {
			return FAIL;
		}
		states[id].req = S_PROPERTY;
		states[id].value = prop;
		states[id].buf = data;
		return call BlockWrite.write[id](getPropAddress(block,prop), &states[id].buf, 4);
	}

	command error_t BlockProperty.get[uint8_t id, uint8_t prop] (im_block_id_t block) {
		if (states[id].req != S_IDLE) {
			return FAIL;
		}
		states[id].req = S_PROPERTY;
		states[id].value = prop;
		return call BlockRead.read[id](getPropAddress(block,prop), &states[id].buf, 4);
	}

	default event void BlockProperty.setDone[uint8_t id, uint8_t prop] (error_t err) {
	}

	default event void BlockProperty.getDone[uint8_t id, uint8_t prop] (error_t err, uint32_t data) {

	}

	/* Block read\write events */
	event void BlockWrite.writeDone[uint8_t id](storage_addr_t addr, void* buf, storage_len_t len, error_t error) {
		switch(states[id].req) {
			case S_ALLOC:
				globalLock = 0;
				states[id].req=S_IDLE;
				// Update next block on success
				nextBlock=nextBlock+1;
				if(nextBlock == blockCount) {
						nextBlock=0;
				}
				signal BlockStorage.allocateDone[id](error,
					call BlockId.create(call LocalAddress.address(), states[id].value % blockCount));
				break;
			case S_WRITE:
				states[id].req=S_IDLE;
				signal BlockStorage.writeDone[id](error);
				break;
			case S_PROPERTY:
				states[id].req=S_IDLE;
				signal BlockProperty.setDone[(uint8_t)states[id].value, id](error); // <-- ZOMG
				break;
			default:
		}
	}

	event void BlockWrite.syncDone[uint8_t id](error_t error) {
	}

	event void BlockWrite.eraseDone[uint8_t id](error_t error) {
		static bool init = TRUE;

		if (init)
		{
			started++;

			if (started < 2)
			{
				if (error == SUCCESS)
					call BlockWrite.erase[id](started);
				else
					signal LocalControl.startDone[id](error);
			} else {
				if (error == SUCCESS)
				{
					init = FALSE;
					signal LocalControl.startDone[id](error);
				}
			}
		}
		else
			signal BlockStorage.eraseDone[id](error);
	}

	event void BlockRead.readDone[uint8_t id](storage_addr_t addr, void* buf, storage_len_t len, error_t error) {
		switch(states[id].req) {
			case S_ALLOC:
				// Check allocation mark
				if(((uint8_t*)buf)[0] != allocationMark) { // If block is free
					// Write allocation mark
					call BlockWrite.write[id](getPhysicalAddress(states[id].value%blockCount),
						(void*)&allocationMark,1);
				} else {
					// Continue search
					states[id].value++;
					if(states[id].value % blockCount  == nextBlock) {
						// No more free storage
						states[id].req=S_IDLE;
						globalLock = 0;
						signal BlockStorage.allocateDone[id](FAIL, 0);
						return;
					}

					// Try next block
					call BlockRead.read[id](getPhysicalAddress(states[id].value%blockCount),
						&states[id].buf,1);
					//Don't change state;
				}
				break;
			case S_READ:
				states[id].req=S_IDLE;
				signal BlockStorage.readDone[id](error);
				break;
			case S_PROPERTY:
				states[id].req=S_IDLE;
				// Returns property data
				signal BlockProperty.getDone[(uint8_t)states[id].value, id](error,states[id].buf); // <-- ZOMG
				break;
			default:
		}
	}

	event void BlockRead.computeCrcDone[uint8_t id](storage_addr_t addr, storage_len_t len,
		uint16_t crc, error_t error) {
	}
	/* default events */
	default command error_t BlockRead.read[uint8_t id] ( storage_addr_t addr, void* buf, storage_len_t len ) {
		return FAIL;
	}
	default command error_t BlockWrite.write[uint8_t id] ( storage_addr_t addr, void* buf, storage_len_t len ) {
		return FAIL;
	}
	default command storage_len_t BlockRead.getSize[uint8_t id] () {
		return 0;
	}
	default command error_t BlockWrite.erase[ uint8_t id ] (uint8_t block) {return FAIL;}
}
